/*
 * Copyright (C) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "MultiSurface_4.h"
#include "MultiSurface_4Manager.h"
#include "av_codec_sample_log.h"
#include "av_codec_sample_error.h"
#include <bits/alltypes.h>

#undef LOG_TAG
#define LOG_TAG "multiSurface"

namespace {
    using namespace std::chrono_literals;
}

int32_t MultiSurface_4::maxNumOfMultiSurface = 4;
std::vector<MultiSurface_4 *> MultiSurface_4::multiSurfaceList;

MultiSurface_4::MultiSurface_4(int32_t id) {
    AVCODEC_SAMPLE_LOGI("Sample id is %{public}i", id + 1);
    this->id = id;
}

MultiSurface_4::~MultiSurface_4() { MultiSurface_4::StartRelease(); }

MultiSurface_4 *MultiSurface_4::getInstance(int32_t id) {
    if (multiSurfaceList.empty()) {
        for (int32_t i = 0; i < maxNumOfMultiSurface; ++i) {
            MultiSurface_4 *emp = new MultiSurface_4(i);
            multiSurfaceList.emplace_back(emp);
        }
    }
    if (id > -1 && id < maxNumOfMultiSurface) {
        return multiSurfaceList.at(id);
    }
    return nullptr;
}

void MultiSurface_4::releaseInstance() { multiSurfaceList.clear(); }

void MultiSurface_4::SetMaxNum(int32_t num) { MultiSurface_4::maxNumOfMultiSurface = num; }

int32_t MultiSurface_4::Create(SampleInfo &sampleInfo) {
    std::lock_guard<std::mutex> lock(mutex_);
    CHECK_AND_RETURN_RET_LOG(!isStarted, AVCODEC_SAMPLE_ERR_ERROR, "Already started.");
    CHECK_AND_RETURN_RET_LOG(dataProducer_ == nullptr && videoDecoder_ == nullptr, AVCODEC_SAMPLE_ERR_ERROR,
                             "Already started.");

    sampleInfo_ = sampleInfo;

    dataProducer_ = DataProducerFactory::CreateDataProducer(sampleInfo_.dataProducerInfo);
    CHECK_AND_RETURN_RET_LOG(dataProducer_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Create data producer failed");

    videoDecoder_ = std::make_unique<VideoDecoder>();
    CHECK_AND_RETURN_RET_LOG(videoDecoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR,
                             "Create video decoder failed, no memory");

    context_ = new CodecUserData;
    context_->sampleInfo = &sampleInfo_;

    int32_t ret = dataProducer_->Init(sampleInfo_);
    CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Data producer init failed");

    sampleInfo.durationTime = sampleInfo_.durationTime;
    sampleInfo_.videoHeight > sampleInfo_.videoWidth ? isVertical = true : isVertical = false;

    ret = videoDecoder_->Create(sampleInfo_.codecMime);
    CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Create video decoder failed");

    ret = videoDecoder_->Config(sampleInfo_, context_);
    CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Decoder config failed");

    releaseThread_ = nullptr;
    AVCODEC_SAMPLE_LOGI("Succeed");
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t MultiSurface_4::Start() {
    std::lock_guard<std::mutex> lock(mutex_);
    CHECK_AND_RETURN_RET_LOG(!isStarted, AVCODEC_SAMPLE_ERR_ERROR, "Already started.");
    CHECK_AND_RETURN_RET_LOG(context_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR, "Already started.");
    CHECK_AND_RETURN_RET_LOG(dataProducer_ != nullptr && videoDecoder_ != nullptr, AVCODEC_SAMPLE_ERR_ERROR,
                             "Already started.");

    int32_t ret = videoDecoder_->Start();
    CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, ret, "Decoder start failed");

    isStarted = true;
    isPause = false;

    decInputThread_ = std::make_unique<std::thread>(&MultiSurface_4::DecInputThread, this);
    decOutputThread_ = std::make_unique<std::thread>(&MultiSurface_4::DecOutputThread, this);
    if (decInputThread_ == nullptr || decOutputThread_ == nullptr) {
        AVCODEC_SAMPLE_LOGE("Create thread failed");
        StartRelease();
        return AVCODEC_SAMPLE_ERR_ERROR;
    }

    AVCODEC_SAMPLE_LOGI("Succeed");
    doneCond_.notify_all();
    return AVCODEC_SAMPLE_ERR_OK;
}


void MultiSurface_4::Release() {
    std::lock_guard<std::mutex> lock(mutex_);
    isStarted = false;
    if (decInputThread_ && decInputThread_->joinable()) {
        decInputThread_->join();
        decInputThread_.reset();
    }
    if (decOutputThread_ && decOutputThread_->joinable()) {
        decOutputThread_->join();
        decOutputThread_.reset();
    }

    if (videoDecoder_ != nullptr) {
        videoDecoder_->Stop();
        videoDecoder_->Release();
        videoDecoder_.reset();
        AVCODEC_SAMPLE_LOGI("Video decoder release successful");
    }
    if (sampleInfo_.window != nullptr) {
        OH_NativeWindow_DestroyNativeWindow(sampleInfo_.window);
        sampleInfo_.window = nullptr;
    }
    if (context_ != nullptr) {
        delete context_;
        context_ = nullptr;
    }
    if (dataProducer_ != nullptr) {
        dataProducer_.reset();
    }
    if (outputFile_ != nullptr) {
        outputFile_.reset();
    }

    doneCond_.notify_all();
    AVCODEC_SAMPLE_LOGI("Succeed");
}

void MultiSurface_4::DecInputThread() {
    while (true) {
        CHECK_AND_BREAK_LOG(isStarted, "Decoder input thread out");
        std::unique_lock<std::mutex> lock(context_->inputMutex_);
        bool condRet = context_->inputCond_.wait_for(
            lock, 5s, [this]() { return !isPause.load() && (!isStarted || !context_->inputBufferInfoQueue_.empty()); });
        CHECK_AND_BREAK_LOG(isStarted, "Work done, thread out");
        CHECK_AND_CONTINUE_LOG(!context_->inputBufferInfoQueue_.empty(),
                               "Buffer queue is empty, continue, cond ret: %{public}d", condRet);

        CodecBufferInfo bufferInfo = context_->inputBufferInfoQueue_.front();
        context_->inputBufferInfoQueue_.pop();
        context_->inputFrameCount_++;
        lock.unlock();

        int32_t ret = dataProducer_->ReadSample(bufferInfo);
        CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Read frame failed, thread out");
        AVCODEC_SAMPLE_LOGW("In buffer count: %{public}u, size: %{public}d, flag: %{public}u, pts: %{public}" PRId64,
                            context_->inputFrameCount_, bufferInfo.attr.size, bufferInfo.attr.flags,
                            bufferInfo.attr.pts);

        if ((bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS) && isLoop) {
            bufferInfo.attr.flags = AVCODEC_BUFFER_FLAGS_NONE;
            int32_t ret = dataProducer_->Seek(0);
            CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Loop failed, thread out");
            ret = dataProducer_->ReadSample(bufferInfo);
            CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Read frame failed, thread out");
        }

        ThreadSleep();

        ret = videoDecoder_->PushInputData(bufferInfo);
        CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Push data failed, thread out");
        CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), "Push EOS frame, thread out");
    }
    AVCODEC_SAMPLE_LOGI("Exit, frame count: %{public}u", context_->inputFrameCount_);
    StartRelease();
}

void MultiSurface_4::DecOutputThread() {
    struct timespec startTime;
    struct timespec endTime;
    while (true) {
        clock_gettime(CLOCK_MONOTONIC, &startTime);
        CHECK_AND_BREAK_LOG(isStarted, "Decoder output thread out");
        std::unique_lock<std::mutex> lock(context_->outputMutex_);
        bool condRet = context_->outputCond_.wait_for(lock, 5s, [this]() {
            return !isPause.load() && (!isStarted || !context_->outputBufferInfoQueue_.empty());
        });
        CHECK_AND_BREAK_LOG(isStarted, "Decoder output thread out");
        CHECK_AND_CONTINUE_LOG(!context_->outputBufferInfoQueue_.empty(),
                               "Buffer queue is empty, continue, cond ret: %{public}d", condRet);

        CodecBufferInfo bufferInfo = context_->outputBufferInfoQueue_.front();
        context_->outputBufferInfoQueue_.pop();
        CHECK_AND_BREAK_LOG(!(bufferInfo.attr.flags & AVCODEC_BUFFER_FLAGS_EOS), "Catch EOS, thread out");
        context_->outputFrameCount_++;
        AVCODEC_SAMPLE_LOGW("Out buffer count: %{public}u, size: %{public}d, flag: %{public}u, pts: %{public}" PRId64,
                            context_->outputFrameCount_, bufferInfo.attr.size, bufferInfo.attr.flags,
                            bufferInfo.attr.pts);
        lock.unlock();

        if (autoSwitchSurface && (context_->outputFrameCount_ % (int32_t)sampleInfo_.frameRate == 0)) {
            switchSurfaceFlag = (switchSurfaceFlag == 1) ? 0 : 1;
            AVCODEC_SAMPLE_LOGI("surface change %{public}i", switchSurfaceFlag);
            if (switchSurfaceFlag) {
                switch (sampleId) { 
                    case 1 : {
                        SetSurface("Surface4_2");
                        break;
                    }
                    case 2 : {
                        SetSurface("Surface4_4");
                        break;
                    }
                    case 3 : {
                        SetSurface("Surface4_6");
                        break;
                    }
                    case 4 : {
                        SetSurface("Surface4_8");
                        break;
                    }
                    default: {
                        AVCODEC_SAMPLE_LOGI("sample id error %{public}i", sampleId);
                        break;
                    }
                }
            } else {
                switch (sampleId) {
                case 1: {
                        SetSurface("Surface4_1");
                        break;
                }
                case 2: {
                        SetSurface("Surface4_3");
                        break;
                }
                case 3: {
                        SetSurface("Surface4_5");
                        break;
                }
                case 4: {
                        SetSurface("Surface4_7");
                        break;
                }
                default: {
                        AVCODEC_SAMPLE_LOGI("sample id error %{public}i", sampleId);
                        break;
                }
                }
            }
        }

        int32_t ret = videoDecoder_->FreeOutputData(bufferInfo.bufferIndex, !(sampleInfo_.codecRunMode & 0b01));
        CHECK_AND_BREAK_LOG(ret == AVCODEC_SAMPLE_ERR_OK, "Decoder output thread out");
        clock_gettime(CLOCK_MONOTONIC, &endTime);
        long diffMs = (endTime.tv_sec - startTime.tv_sec) * MICROSECOND;
        auto sleepTime = MICROSECOND / sampleInfo_.frameRate - diffMs;
        usleep(sleepTime);
    }
    AVCODEC_SAMPLE_LOGI("Exit, frame count: %{public}u", context_->outputFrameCount_);
    StartRelease();
}

int32_t MultiSurface_4::Stop() {
    MultiSurface_4::Release();
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t MultiSurface_4::Pause() {
    isPause.store(true);
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t MultiSurface_4::Play() {
    isPause.store(false);
    context_->inputCond_.notify_all();
    context_->outputCond_.notify_all();
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t MultiSurface_4::VideoSeek(int64_t currentTime) {
    int64_t milliseconds = currentTime * MILLISECONDS;
    int32_t ret = dataProducer_->Seek(milliseconds);
    CHECK_AND_RETURN_RET_LOG(ret == AVCODEC_SAMPLE_ERR_OK, AVCODEC_SAMPLE_ERR_ERROR, "video seek failed");
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t MultiSurface_4::Loop(bool loopFlag) {
    loopFlag ? isLoop.store(true) : isLoop.store(false);
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t MultiSurface_4::SetSurface(const std::string id) {
    videoDecoder_->SetSurface(NativeXComponentSample::PluginManager::GetInstance()->multiWindowsMap.at(id));
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t MultiSurface_4::SetSurfaceNative(bool isAuto, int32_t id) {
    isAuto ? autoSwitchSurface.store(true) : autoSwitchSurface.store(false);
    sampleId = id;
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t MultiSurface_4::SetWindowManager(const std::string id) {
    //    surfaceManager_ =
    //    surfaceManager_.emplace(id,videoDecoder_);
    return AVCODEC_SAMPLE_ERR_OK;
}

int32_t MultiSurface_4::GetWindowManager(const std::string id) { return AVCODEC_SAMPLE_ERR_OK; }
